package model;

import java.util.List;
import java.util.ArrayList;

/**
 * LevelEditor class provides functions to easily create and modify
 * a GameLevel object. LevelEditor also stores a list of EditorListeners,
 * and updates all Listeners whenever a change to the level structure has
 * been made.
 * 
 * @author Alexander Craig
 */
public class LevelEditor {
	/** a list of all listeners to this editor object */
	private List<EditorListener> listeners;
	/** the currently selected tool for this level editor */
	private EditorTool tool;
	/** the direction for the currently selected tool to be used in */
	private Direction dir;
	/** the type of spawn point to generate */
	private GameEntity spawnType;
	/** the GameLevel currently being edited */
	private GameLevel level;
	/** the tile currently selected (used for performing editing operations) */
	private Tile selectedTile;
	/** a secondary selected tile used for some operations (adding a portal, for instance) */
	private Tile secondaryTile;
	
	
	/**
	 * Creates a new, empty LevelEditor
	 */
	public LevelEditor() {
		level = null;
		selectedTile = null;
		secondaryTile = null;
		tool = null;
		dir = Direction.NORTH;
		spawnType = GameEntity.PACMAN;
		listeners = new ArrayList<EditorListener>();
	}
	
	/**
	 * Sets the spawn type of the next spawn point placed to the given game entity type.
	 * @param ent	the type of spawn to place next
	 */
	public void setSpawnType(GameEntity ent) {
		spawnType = ent;
	}
	
	/**
	 * Sets the level that this level editor should be editting
	 * @param level	the level to edit
	 */
	public void setLevel(GameLevel level) {
		this.level = level;
		for(EditorListener l : listeners) {
			l.newLevel(new EditorEvent(this));
		}
	}
	
	/**
	 * Sets the number of dots required to pass the level.
	 * @param dots	the number of dots remaining to pass the level
	 */
	public void setPassDots(int dots) {
		if (dots >= 0 && level != null) {
			level.getMaze().setPassDots(dots);
			
			for(EditorListener l : listeners) {
				l.mazeChange(new EditorEvent(this));
			}
		}
	}
	
	/**
	 * @return	the level currently being edited
	 */
	public GameLevel getLevel() {
		return level;
	}
	
	/**
	 * Attempts to add a tile at the specified x,y, if one does not exist already
	 * @param x	the x coordinate at which to add a tile
	 * @param y	the y coordinate at which to add a tile
	 */
	public void addTile(int x, int y) {
		if (level != null) {
			level.getMaze().addTile(x, y);
			for(EditorListener l : listeners) {
				l.mazeChange(new EditorEvent(this));
			}
		}
	}
	
	public void useTool() {
		if(level == null || dir == null || tool == null || selectedTile == null)
			return;

		switch(tool) {
		case ADD_WALL:
			level.getMaze().removeExit(selectedTile.getXCoord(), selectedTile.getYCoord(), dir, false);
			break;
		case ADD_WALL_LINKED:
			level.getMaze().removeExit(selectedTile.getXCoord(), selectedTile.getYCoord(), dir, true);
			break;
		case CLEAR_WALL:
			level.getMaze().addExit(selectedTile.getXCoord(), selectedTile.getYCoord(), dir, false);
			break;
		case CLEAR_WALL_LINKED:
			level.getMaze().addExit(selectedTile.getXCoord(), selectedTile.getYCoord(), dir, true);
			break;
		case ADD_PORTAL:
			if (secondaryTile == null)
				return;
			level.getMaze().addPortal(selectedTile.getXCoord(), selectedTile.getYCoord(),
					secondaryTile.getXCoord(), secondaryTile.getYCoord(), dir);
			break;
		case REMOVE_TILE:
			level.removeSpawnTile(selectedTile);
			level.getMaze().removeTile(selectedTile.getXCoord(), selectedTile.getYCoord());
			break;
		case ADD_SPAWN:
			if (spawnType == GameEntity.PACMAN) {
				level.setPlayerStart(selectedTile);
			}
			level.addSpawnPoint(selectedTile, spawnType);
			break;
		case CLEAR_SPAWN:
			level.removeSpawnTile(selectedTile);
			break;
		}
		
		for(EditorListener l : listeners) {
			l.mazeChange(new EditorEvent(this));
		}
	}
	
	/**
	 * Generates a new level, and discards the old
	 * @param title	the title of the new level
	 * @param x	the number of columns in the new level
	 * @param y	the number of rows in the new level
	 */
	public void newLevel(String title, int x, int y) {
		if(title != null && x >= 1 && y >= 1) {
			selectedTile = null;
			secondaryTile = null;
			Maze maze = new Maze(x, y, true);
			level = new GameLevel(maze, title);
			for(EditorListener l : listeners) {
				l.newLevel(new EditorEvent(this));
			}
		}
	}
	
	/**
	 * Sets the selected tile to the passed tile
	 * @param t	the tile to selected
	 */
	public void setSelectedTile(Tile t) {
		selectedTile = t;
		secondaryTile = null;
		for(EditorListener l : listeners) {
			l.selectionChange(new EditorEvent(this));
		}
	}
	
	/**
	 * Converts the passed x,y coords into a tile in the loaded maze,
	 * then selects that tile.
	 * @param x		the x coordinate of the desired tile
	 * @param y		the y coordinate of the desired tile
	 */
	public void setSelectedTile(int x, int y) {
		if(level != null) {
			setSelectedTile(level.getMaze().getTile(x, y));
		}
	}
	
	/**
	 * Sets the secondary selected tile to the passed tile
	 * @param t	the tile to selected
	 */
	public void setSecondaryTile(Tile t) {
		secondaryTile = t;
		for(EditorListener l : listeners) {
			l.selectionChange(new EditorEvent(this));
		}
	}
	
	/**
	 * Converts the passed x,y coords into a tile in the loaded maze,
	 * then selects that tile as the secondary selection.
	 * @param x		the x coordinate of the desired tile
	 * @param y		the y coordinate of the desired tile
	 */
	public void setSecondaryTile(int x, int y) {
		if(level != null) {
			setSecondaryTile(level.getMaze().getTile(x, y));
		}
	}
	
	/**
	 * Returns true if the passed x,y coordinates correspond to the selected tile
	 * @param x	the x coordinate to check
	 * @param y	the y coordinate to check
	 * @return	true if the passed x,y coords match those of the selected tile
	 */
	public boolean checkSelected(int x, int y) {
		if(x < 0 || x >= level.getMaze().getMaxX() || y < 0 || y >= level.getMaze().getMaxY()) {
			return false;
		}
		
		Tile t = level.getMaze().getTile(x, y);
		if (t == null) {
			return false;
		}
		
		if(t == selectedTile) {
			return true;
		}
		return false;
	}
	
	/**
	 * Sets the editor to use the specified tool.
	 * @param tool	the tool to use (see EditorTool)
	 */
	public void setTool(EditorTool tool) {
		this.tool = tool;
		for(EditorListener l : listeners) {
			l.toolChange(new EditorEvent(this));
		}
	}
	
	/**
	 * @return	the tool this editor is currently using
	 */
	public EditorTool getTool() {
		return tool;
	}
	
	/**
	 * Sets the editor's modification direction to the passed direction
	 * @param dir	the direction to change exits in
	 */
	public void setDirection(Direction dir) {
		this.dir = dir;
	}
	
	/**
	 * @return	the direction of exit this editor is currently set to modify
	 */
	public Direction getDirection() {
		return dir;
	}
	
	/**
	 * @return	the selected Tile, or null if no tile is selected
	 */
	public Tile getSelectedTile() {
		return selectedTile;
	}
	
	/**
	 * @return	the secondary selected Tile, or null if no tile is selected
	 */
	public Tile getSecondaryTile() {
		return secondaryTile;
	}
	
	/**
	 * @return	the maze currently being edited, or null if no level is open
	 */
	public Maze getMaze() {
		if(level != null) {
			return level.getMaze();
		}
		return null;
	}
	
	/**
	 * Checks the current level to see if it is a valid, playable level.
	 */
	public boolean hasValidLevel(){
		if (level == null)
			return false;
		return level.isValid();
	}
	
	/**
	 * Adds a editor listener to the list of objects listening
	 * on this LevelEditor.
	 * @param listener	the listener to add to the list
	 */
	public void addEditorListener(EditorListener listener) {
		listeners.add(listener);
	}
	
	/**
	 * Removes a game listener to the list of objects listening
	 * on this GameModel.
	 * @param listener	the listener to remove the list
	 */
	public void removeEditorListener(EditorListener listener) {
		listeners.remove(listener);
	}
	
	/**
	 * Removes all listeners of this LevelEditor.
	 */
	public void removeAllListeners() {
		listeners.clear();
	}
}
